/*
* File: telnet.cpp
* Version: 1.0
* Last modified on 2014-03-18 15:00
* Contributors : Nicklas Wallman (s131448), Olof Ivarsson(s132046), Oscar Ernstsson(s132952), Oscar Pehrson(s131469), Martin Jakobsson(s133082)
*/

#include "telnet.h"
#include <stdio.h>
#include <winsock2.h>
#include <Ws2tcpip.h>
#ifdef TELNET_WEBSRV
#include "webserver.h"
#endif
#include "config.h"
#include "logfile.h"

#define TELNET_BUFLEN 20
#define OK            0
#define CMD_MAX_LEN   16

#define NEW_LINE        0x0a
#define CARRIAGE_RETURN 0x0d
#define NULL_CHR        0x00
#define BACKSPACE       0x08
#define SPACE           0x20
#define DELETE          0x7f

extern configT srvConfig;
extern BOOL webSrvRunning;

static BOOL telnetAuthed;

/*
 * Function: initTelnet
 * Usage: initTelnet();
 * --------------------------
 * This function initializes telnet (sets some default variables to FALSE, etc). For an example, it sets telnetAuthed to false.
 */

static void initTelnet(void);

/*
 * Function: destructTelnetSession
 * Usage: destructTelnetSession();
 * --------------------------
 * Destructs telnet.
 */
static void destructTelnetSession(void);

/*
 * Function: createTelnetSock
 * Usage: SOCKET s = createTelnetSock(port);
 * --------------------------
 * Creates a server socket and starts listening on given port. 
 */

static int createTelnetSock(short listenPort);

/*
 * Function: authorizeTelnet
 * Usage: authorizeTelnet(socket, password);
 * --------------------------
 * Checks whether the given password is the same as the admin password (in config file). If it is, it sets telnetAuthed to TRUE.
 */

static void authorizeTelnet(SOCKET s, char *pwd);

/*
 * Function: parseCmd
 * Usage: parseCmd(telnetMessage, cmdOut);
 * --------------------------
 * It parses a command of the following format: %s %s and sets the variable cmdOut to the first word occurrence.
 */

static void parseCmd(char *telnetMsg, char *cmdOut);

/*
 * Function: parseVal
 * Usage: val = parseVal(telnetMessage);
 * --------------------------
 * It parses a value of the following format: %s %s and returns the value (second word after the space).
 */

static char *parseVal(char *telnetMsg);

/*
 * Function: getCmdType
 * Usage: type = getCmdType(telnetMessage);
 * --------------------------
 * This function checks the command type and returns an int.
 */

static unsigned int getCmdType(TELNETMSG msg);

/*
 * Function: getMsg
 * Usage: telnetMessage = getMsg(clientSocket);
 * --------------------------
 * This function reads and handles messages from telnet client (it deals with backspace and as well as some other characters). 
 * Note: Once the message is not needed anymore, you need to free it to prevent memory leaks.
 */

static char *getMsg(SOCKET clientSocket);

/*
 * Function: handleTelnetcli
 * Usage: handleTelnetcli(clientSocket);
 * --------------------------
 * Handles the client (accepts the client socket, closes it once the client has closed the connection), etc.
 */

static void handleTelnetcli(SOCKET clientSocket);

/*
 * Function: sendWelcomeMsg
 * Usage: sendWelcomeMsg(clientSocket)
 * --------------------------
 * Sends a welcome message to the client (menu, etc).
 */

static void sendWelcomeMsg(SOCKET clientSocket);

/*
 * Function: isActive
 * Usage: isActive(clientSocket);
 * --------------------------
 * Checks whether the web server is on or off, and sends an appropriate message to the client.
 */
static void isActive(SOCKET clientSocket);

/*
 * Function: manageServer
 * Usage: manageServer(clientSocket, action);
 * --------------------------
 * Sets the web server to on or off, depending on action (which must be either START or STOP).
 */

static void manageServer(SOCKET clientSocket, char *action);

/*
 * Function: ensureLoggedIn
 * Usage: status = ensureLoggedIn(clientSocket);
 * --------------------------
 * Checks if the user has authorized.
 */
static BOOL ensureLoggedIn(SOCKET clientSocket);

/*
 * Function: convertToUpperCase
 * Usage: convertToUpperCase(str);
 * --------------------------
 * Converts the given string to uppercase and provides some error handling. The string must be modifiable (e.g. memory can not just be read-only).
 */
static void convertToUpperCase(char *str);

/*
 * Function: invalidCmd
 * Usage: invalidCmd(clientSocket);
 * --------------------------
 * Sends a message to the client that the CMD was invalid.
 */
static void invalidCmd(SOCKET clientSocket);

/*
 * Function: sendLogFile
 * Usage: sendLogFile(clientSocket);
 * --------------------------
 * Sends the web server's log file to the client.
 */
static void sendLogFile(SOCKET clientSocket);



static void destructTelnetSession(void)
{
	telnetAuthed = FALSE;
}
static void sendWelcomeMsg(SOCKET s)
{
	char *msg;
	msg = "Welcome! The following commands are available: \r\n\nAUTHORIZE <PASSWORD> \r\nSERVER <START OR STOP>\r\nISACTIVE \r\nGETLOGFILE\r\n";
	send(s, msg, strlen(msg), 0);
}

static void invalidCmd(SOCKET s)
{
	char *msg;
	msg = "\r\nInvalid command. Please try again.\r\n";
	send(s, msg, strlen(msg), 0);
}

static BOOL ensureLoggedIn(SOCKET s)
{
	char *msg;
	if (!telnetAuthed)
	{
		msg = "\r\nYou need to authorize before you can use any other commands. \r\n";
		send(s, msg, strlen(msg), 0);
		return false;
	}
	return true;
}

static void sendLogFile(SOCKET s)
{
	char *logData, *error;
	int len;

	if (ensureLoggedIn(s))
	{
		logData = getLogFile();
		if (logData != NULL)
		{
			len = strlen(logData);
			send(s, logData, len, 0);
		}
		else
		{
			error = "\r\nNo log data could be found.\r\n";
			send(s, error, strlen(error), 0);
		}

		free(logData);
	}
}

static void manageServer(SOCKET s, char *action)
{
	if (ensureLoggedIn(s))
	{
		if (strcmp(action, "START") == OK)
		{
			webSrvRunning = TRUE;
		} else if (strcmp(action, "STOP") == OK)
		{
			webSrvRunning = FALSE;
		}
	}
}

static void isActive(SOCKET s)
{
	char *telnetResponse;

	if (ensureLoggedIn(s))
	{
		if (webSrvRunning)
		{
			telnetResponse = "\r\nResponding to incoming requests. \r\n";
		}
		else
		{
			telnetResponse = "\r\nIgnoring incoming requests. \r\n";
		}
		send(s, telnetResponse, strlen(telnetResponse), 0); 
	}
}

void initTelnet(void)
{
	telnetAuthed = FALSE;
}
static char *getMsg(SOCKET s)
{
	char *buf, *tmpbuf, ch, backSpace, carrRet, space;
	int recvlen, c, initMemSize, n;

	initMemSize = 10;
	n = 0;
	buf = (char *) malloc(initMemSize + 1);
	ch = ' ';
	backSpace = BACKSPACE;
	carrRet = CARRIAGE_RETURN;
	space = SPACE;

	while (ch != NEW_LINE)
	{
		if (n == initMemSize)
		{
			initMemSize *= 2;
			tmpbuf = (char *) malloc(initMemSize + 1);
			strncpy(tmpbuf, buf, n);
			free(buf);
			buf = tmpbuf;
		}
		recvlen = recv(s, &ch, 1, 0);
		if (recvlen <= 0)
		{
			return (NULL);
		}

		switch (ch)
		{
		case CARRIAGE_RETURN:
			buf[n++] = NULL_CHR;
			break;
		case BACKSPACE:
			if (n > 0) buf[n--] = NULL_CHR;
			send(s, &space, 1, 0);
			send(s, &backSpace, 1, 0);
			break;
		default: 
			// If client is Putty and if it's in Active Mode, it will attempt to negotiate.
			if ((ch >= 48 && ch <= 128) || (ch == ' ' && n > 0)) buf[n++] = ch;
			break;
		}
	}

	if (n == 0)
	{
		free(buf);
		return (NULL);
	}

	buf[n++] = '\0';


	printf("\nTelnet message received: %s \n\n", buf);
	return buf;
}

static void handleTelnetcli(SOCKET clientSock)
{
	TELNETMSG msg, val;
	unsigned int cmdType;

	sendWelcomeMsg(clientSock);

	while (TRUE)
	{
		msg  = getMsg(clientSock);

		// If client closed the connection, the function getMsg will return NULL.
		if (msg == NULL) break;

		val             = parseVal(msg);
		cmdType = getCmdType(msg);

		switch (cmdType)
		{
		case AUTHORIZE:  authorizeTelnet(clientSock, val);  break;
		case SERVER:     manageServer(clientSock, val);     break;
		case ISACTIVE:   isActive(clientSock);                      break;
		case GETLOGFILE: sendLogFile(clientSock);               break;
		case BADCMD:     invalidCmd(clientSock);                break;
		default: 
			break;
		}
		free(msg);
	}
}

void runTelnet(short listenPort)
{
	SOCKET s, connSock;
	struct sockaddr connData;
	char *msg, *telnetResp, host[1024], service[20];
	int recvlen, cmdType, clientAddrLen;

	initTelnet();
	s = createTelnetSock(listenPort);
	while (TRUE)
	{
		clientAddrLen = sizeof(connData);
		if ( (connSock = accept(s, &connData, &clientAddrLen)) == INVALID_SOCKET )
		{
			fprintf(stderr, "Could not accept client. %d", WSAGetLastError());
			exit(EXIT_FAILURE);
		}
		else
		{
			getnameinfo(&connData, clientAddrLen, host, sizeof(host), service, sizeof(service), 0);
			printf("\nTelnet: Client %s has connected. \n", host);

			// Serve client
			handleTelnetcli(connSock);
			closesocket(connSock);
			destructTelnetSession();
			printf("\nTelnet: Client closed the connection.\n");
		}
	}
}

static void authorizeTelnet(SOCKET s, char *pwd)
{
	char *msg, *telnetPwd;

	telnetPwd = srvConfig.telnetPwd;
	if (strcmp(pwd, telnetPwd) == OK) 
	{
		telnetAuthed = TRUE;
		msg = "\r\nYou have successfully logged in.\r\n";
	}
	else
	{
		msg = "\r\nInvalid password. Please try again.\r\n";
	}
	send(s, msg, strlen(msg), 0);
}


static void parseCmd(char *telnetMsg, char *cmdOut)
{
	int count = 0;
	char *i;

	for (i = telnetMsg; *i != ' ' && count < strlen(telnetMsg); i++)
	{
		cmdOut[count] = *i;
		count++;
	}
	cmdOut[count++] = '\0';
}

static char *parseVal(char *telnetMsg)
{
	char *val;

	val = strtok(telnetMsg, " ");
	val = strtok(NULL, "=");

	if (val == NULL) val = "\0";
	return val;
}

static unsigned int getCmdType(char *telnetMsg)
{
	char cmd[CMD_MAX_LEN ] = "";

	if (strlen(telnetMsg) > CMD_MAX_LEN-1)
		return BADCMD;

	parseCmd(telnetMsg, cmd);
	convertToUpperCase(cmd);
	if (strcmp(cmd, "AUTHORIZE") == OK)
	{
		return AUTHORIZE;
	}
	else if (strcmp(cmd, "SERVER") == OK)
	{
		return SERVER;
	}
	else if (strcmp(cmd, "ISACTIVE") == OK)
	{
		return ISACTIVE;
	}
	else if(strcmp(cmd, "GETLOGFILE") == OK)
	{
		return GETLOGFILE;
	}
	else
	{
		return BADCMD;
	}
}
static int createTelnetSock(short listenPort)
{
	SOCKET s;
	struct sockaddr_in srvaddr;

	s = 0;
	memset(&srvaddr, 0, sizeof(srvaddr));
	srvaddr.sin_family              = AF_INET;
	srvaddr.sin_addr.s_addr = INADDR_ANY;
	srvaddr.sin_port            = htons(listenPort);

	if ( (s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET )
	{
		fprintf(stderr, "Unable to create server socket for telnet.");
		return 2;
		exit(EXIT_FAILURE);
	}

	if (bind(s, (sockaddr*)&srvaddr, sizeof(srvaddr)) == SOCKET_ERROR)
	{
		fprintf(stderr, "Unable to bind socket.");
		exit(EXIT_FAILURE);
	}

	if (listen(s, 10) == SOCKET_ERROR)
	{
		fprintf(stderr, "Unable to listen.");
		exit(EXIT_FAILURE);
	}

	return s;
}

static void convertToUpperCase(char *str)
{
	int i;

	if (str == NULL)
		return;

	for (i = 0; str[i] != '\0'; i++)
	{
		str[i] = toupper(str[i]);
	}
}